package com.chenfan.ccp.plug.log.handler;

import com.chenfan.ccp.common.listener.AbstractEventListener;
import com.chenfan.ccp.executor.SmartExecutorService;
import com.chenfan.ccp.plug.log.util.TraceIdTaskDecorator;
import com.chenfan.ccp.util.LogUtil;
import com.chenfan.ccp.util.start.ApplicationContextUtil;
import com.chenfan.ccp.util.tool.PushUtil;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StopWatch;

import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;

/**
 * 动态数据源切换的拦截
 */

@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
public class DynamicDataSourceInterceptor extends AbstractEventListener implements Interceptor {
    private final Pattern selectPattern = Pattern.compile("select(\\s.*)from(\\s.*(where\\s.*)?)", Pattern.CASE_INSENSITIVE);
    private final int SQL_SPLIT_LENGTH = 30;
    private final int MAX_RESULT_LENGTH = 20;
    private final int ZERO = 0;
    private int sqlAlarmTime = 15;
    private Logger log = LoggerFactory.getLogger(this.getClass());

    private final ExecutorService executorService = SmartExecutorService.newFixedThreadPool(Runtime.getRuntime().availableProcessors()*2);

    {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> executorService.shutdown()));
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StopWatch timeWatch = new StopWatch();
        timeWatch.start();

        Statement statement;
        Object[] args = invocation.getArgs();
        Object firstArg = args[0];
        if (Proxy.isProxyClass(firstArg.getClass())) {
            statement = (Statement) SystemMetaObject.forObject(firstArg).getValue("h.statement");
        } else {
            statement = (Statement) firstArg;
        }

        MetaObject stmtMetaObj = SystemMetaObject.forObject(statement);
        try {
            statement = (Statement) stmtMetaObj.getValue("stmt.statement");
        } catch (Exception e) {

        }

        if (stmtMetaObj.hasGetter("delegate")) {
            try {
                statement = (Statement) stmtMetaObj.getValue("delegate");
            } catch (Exception e) {

            }
        }

        String sql = statement.toString();
        sql = sql.replaceAll("[\\s]+", " ");
        int index = indexOfSqlStart(sql);
        if (index > 0) {
            sql = sql.substring(index);
        }


        //send event
//        sendEvent(CollectEventListener.builder().
//                traceId(TraceIdUtil.getTraceId()).
//                data(sql)
//                .type(BusinessLogTypeEnums.MYSQL).build());

        Object result = invocation.proceed();
        timeWatch.stop();
        final long totalTimeMillis = timeWatch.getTotalTimeMillis();
        String loginfo = String.format("    [sql]: ==> %s <==   |   [time]: ==> %s ms <==   |   [size]: ==> %s <==",
                this.formatSql(sql),totalTimeMillis, result instanceof Collection ? ((Collection) result).size() : ZERO) ;
        this.log.info(loginfo);

        if (totalTimeMillis >= sqlAlarmTime*1000){
            if((!"chenfan-cloud-filestore".equals(ApplicationContextUtil.getServiceName()) && !"chenfan-cloud-litchi".equals(ApplicationContextUtil.getServiceName()))
                    || ("chenfan-cloud-filestore".equals(ApplicationContextUtil.getServiceName()) && totalTimeMillis>300*1000)
            ){
                executorService.execute(new TraceIdTaskDecorator().decorate(() -> PushUtil.getInstance().send(PushUtil.PushType.DINGDING, "", LogUtil.getSqlMsg("sql时长告警",totalTimeMillis))));
            }
        }
        return result;
    }



    /**
     * 获取sql语句开头部分
     *
     * @param sql
     * @return
     */
    private int indexOfSqlStart(String sql) {
        String upperCaseSql = sql.toUpperCase();
        Set<Integer> set = new HashSet<>();
        set.add(upperCaseSql.indexOf("SELECT "));
        set.add(upperCaseSql.indexOf("UPDATE "));
        set.add(upperCaseSql.indexOf("INSERT "));
        set.add(upperCaseSql.indexOf("DELETE "));
        set.remove(-1);
        if (set.isEmpty()) {
            return -1;
        }
        List<Integer> list = new ArrayList<>(set);
        Collections.sort(list, Integer::compareTo);
        return list.get(0);
    }

    /**
     * 格式化sql
     *
     * @param sql
     * @return
     */
    private String formatSql(String sql) {
//        if (sql.toLowerCase().startsWith("select")) {
//            Matcher selectMatcher = this.selectPattern.matcher(sql.toLowerCase());
//            if (selectMatcher.find() && selectMatcher.group(1).length() > SQL_SPLIT_LENGTH
//                    && (!selectMatcher.group(2).toLowerCase().contains("count("))) {
//                sql = "select  * from " + selectMatcher.group(2);
//            }
//        }
        return beautifySql(sql);
    }


    private String beautifySql(String sql) {
        sql = sql.replaceAll("[\\s\n ]+", " ");
        return sql;
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }


    @Override
    public Object getListener() {
        return this;
    }
}
